package br.ufrgs.dbproject.pathcache;

import java.util.Stack;

/**
 * Abstraction of a XML elements path
 * 
 * @author diego
 */
@SuppressWarnings("unchecked")
public class Path {

	private final Stack<String> elementsStack;
	private String stringifiedPath;
	
	/**
	 * Creates an empty {@link Path}
	 */
	private Path() {
		this.elementsStack = new Stack<String>();
	}
	
	/**
	 * Creates a customized path
	 * 
	 * @param path stack holding the path
	 */
	private Path(Stack<String> path) {
		this.elementsStack = path;
	}
	
	/**
	 * Creates a {@link Path} with custom elements Stack and it's stringified version.
	 *  
	 * @param path the elements stack
	 * @param stringifiedStack string representation of the path
	 */
	private Path(Stack<String> path, String stringifiedStack) {
		this.elementsStack = path;
		this.stringifiedPath = stringifiedStack;
	}
	
	/**
	 * Creates a {@link Path} from a string representation of it.
	 *  
	 * @param path the string representation.
	 * @return the new {@link Path}.
	 */
	public static Path createPathFromString(String path) {
		
		Stack<String> pathStack = new Stack<String>();
		String[] pathElements = path.split("\\/");
		for (String element : pathElements) {
			pathStack.push(element);
		}
		return new Path(pathStack, path);
	}
	
	/**
	 * Creates a {@link Path} from a given elements stack.
	 * 
	 * @param pathStack the elements stack.
	 * @return the new {@link Path}.
	 */
	public static Path createPathFromStack(Stack<String> pathStack) {
		return new Path(pathStack);
	}
	
	/**
	 * Creates an empty {@link Path}.
	 * 
	 * @return the empry {@link Path}.
	 */
	public static Path createEmptyPath() {
		return new Path();
	}

	/**
	 * Concatenates a new element at the end of the {@link Path}.
	 * 
	 * @param element the new element to be concatenated.
	 * @return the new {@link Path} resulted from the operation.
	 */
	public Path concat(String element) {
		
		if (element.isEmpty()) {
			return this;
		}
		
		Stack<String> newPath = (Stack<String>) elementsStack.clone();
		newPath.push(element);

		return new Path(newPath);
	}
	
	/**
	 * Removes the element at the tail of the path.
	 * 
	 * @return a new {@link Path} resulted from the operation.
	 */
	public Path removeTail() {
		
		if (elementsStack.isEmpty()) {
			return this;
		}
		
		Stack<String> newPath = (Stack<String>) elementsStack.clone();
		newPath.pop();
		
		return new Path(newPath);
	}

	/**
	 * Equality operation.
	 * 
	 * <p>
	 * This operation supports comparison between {@link Path} elements and
	 * String representation of a {@link Path}.  
	 * </p>
	 */
	@Override
	public boolean equals(Object obj) {

		if (obj == null) {
			return false;
		}
		
		if (this == obj) {
			return true;
		}
		
		if (obj instanceof String) {
			return toString().equals(obj);
		}
		
		if (obj instanceof Path) {
			Path that = (Path) obj;
			return this.elementsStack.equals(that.elementsStack);
		}
		
		return false;
	}
	
	/**
	 * Lazy initialization of the stringfied version of the {@link Path}.
	 */
	private void lazyStringification() {
		
		if (stringifiedPath == null) {
			StringBuilder builder = new StringBuilder("/");
			for (String localPath : elementsStack) {
				builder.append(localPath).append("/");
			}
			builder.deleteCharAt(builder.length() - 1);
			stringifiedPath = builder.toString();
		}
	}

	/**
	 * Gets the stringified {@link Path}.
	 */
	@Override
	public String toString() {
		lazyStringification();
		return stringifiedPath;
	}
}
